////////////////////////////////////////////////
// CD Driver for OpenGL buffer rendering
////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <math.h>

#include <windows.h>
#include <gl/gl.h>


#define CD_NO_OLD_INTERFACE
#include <cd.h>
#include <cd_private.h>


#define CURVETOL 0.5 //tolerance of which curves are flattened

struct _cdCtxCanvas
{
  cdCanvas* canvas;
  void* winhandle;      //HWND in windows
  void* wincontext;     //HDC in windows
  void* glcontext;      //HGLRC in windows
  int curmode;          //the current opengl mode
  int modeactive;       //if we are inside a begin...end sequence
};

static void debug(char* str) {
        MessageBox(GetForegroundWindow(),str,str,MB_OK);
}

//the platfrom init routines should fill the CtxCanvas structure (except winhandle) _and_ w,h of the canvas it points to

static void begindraw(cdCtxCanvas* ctxcanvas,int mode) {
    if(ctxcanvas->modeactive) {
        if(mode==ctxcanvas->curmode)
            return; //requested mode already active
        glEnd();    //end current mode
    }
    ctxcanvas->modeactive=1;
    ctxcanvas->curmode=mode;
    glBegin(mode);
}

static void enddraw(cdCtxCanvas* ctxcanvas) {
    if(ctxcanvas->modeactive) {
        glEnd();
        ctxcanvas->modeactive=0;
    }
}

#ifdef WIN32
static int cdactivate(cdCtxCanvas* ctxcanvas) {
    HWND hwnd=(HWND)ctxcanvas->winhandle;
    RECT rect;
    int w,h;



    //get current size of window
    GetClientRect(hwnd,&rect);
    w=rect.right - rect.left;
    h=rect.bottom - rect.top;
    ctxcanvas->canvas->w=w;
    ctxcanvas->canvas->h=h;

    //get opengl running on htis context with pixel/unit match
    wglMakeCurrent(ctxcanvas->wincontext,ctxcanvas->glcontext);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0,0,w,h);
    glOrtho(0.0,w,0.0,h,-5.0,5.0);

    //todo:update attributes etc.

    return CD_OK;

}

static void cdflush(cdCtxCanvas* ctxcanvas) {
    enddraw(ctxcanvas);
    glFlush();
    SwapBuffers(ctxcanvas->wincontext);
}

static void cdkillcanvas(cdCtxCanvas* ctxcanvas) {
    //wglMakeCurrent( NULL, NULL );
    wglDeleteContext( ctxcanvas->glcontext );
    ReleaseDC( ctxcanvas->winhandle, ctxcanvas->wincontext );
    free(ctxcanvas);
}

static void platforminit(cdCtxCanvas* ctxcanvas) {
    //for win32 only
    HWND hwnd=(HWND)ctxcanvas->winhandle;


    //initialize opengl

    PIXELFORMATDESCRIPTOR pfd;
    int iFormat;
    HGLRC hrc;
    HDC hdc;

    // get the device context (DC)
    hdc = GetDC( hwnd );

    // set the pixel format for the DC
    ZeroMemory( &pfd, sizeof( pfd ) );
    pfd.nSize = sizeof( pfd );
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
    PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 0;
    pfd.iLayerType = PFD_MAIN_PLANE;
    iFormat = ChoosePixelFormat( hdc, &pfd );
    SetPixelFormat( hdc, iFormat, &pfd );

    // create and enable the render context
    hrc = wglCreateContext( hdc );


    ctxcanvas->glcontext=hrc;
    ctxcanvas->wincontext=hdc;
    ctxcanvas->modeactive=0;

    cdactivate(ctxcanvas);

}
#endif


static void cdcreatecanvas(cdCanvas *canvas, void *data)
{


    cdCtxCanvas* ctxcanvas = (cdCtxCanvas *)malloc(sizeof(cdCtxCanvas));
    ctxcanvas->winhandle=data;

    platforminit(ctxcanvas);

    ctxcanvas->canvas = canvas;
    canvas->ctxcanvas = ctxcanvas;

    //update canvas context. Note that w and h is filled in by platforminit function.
    double res=3.78;
    canvas->w_mm  = INT_MAX*res;
    canvas->h_mm = INT_MAX*res;
    canvas->bpp=24;
    canvas->xres = res;
    canvas->yres = res;


}

static void cdpixel(cdCtxCanvas* ctxcanvas, int x, int y, long color) {
    begindraw(ctxcanvas,GL_POINTS);
    glVertex2i(x,y);
}

static int calclinecorners(cdCtxCanvas* ctxcanvas,int x1,int y1,int x2,int y2,double* diffx,double* diffy) {
    double dx=x2-x1;
    double dy=y2-y1;
    double d=sqrt(dx*dx+dy*dy)*2.0;
    if(d<1.0) {
        //zero length line, what should we do here?
        return 0;
    }


    *diffx=(double)ctxcanvas->canvas->line_width*dy/d;
    *diffy=(double)ctxcanvas->canvas->line_width*dx/d;

    return 1;
}

static void evalellipse(double param,double* x,double* y,void* data) {

    //assumes data contains center x,center y,radius x,radius y
    double* d=(double*)data;
    *x=d[0]+cos(param)*d[2];
    *y=d[1]+sin(param)*d[3];
}


static double distlinepoint(double x1,double y1,double x2,double y2,double px,double py) {
    double dx=x2-x1;
    double dy=y2-y1;
    double sqrlen=dx*dx+dy*dy;
    double s;

    if(sqrlen<1e-9)
        return hypot(y1-py,x1-px);

    s = ((y1 - py) * dx - (x1 - px) * dy) / sqrlen;

    return fabs(s*sqrt(sqrlen));

}

static void docurve_recursive(double x1,double y1,double x2,double y2,double startparam,double endparam,double tol,void* evaldata,void(*evalfunc)(double param,double* x,double* y,void* data),void* callbackdata,void(*callback)(double x,double y,void* data)) {
    double midparam,mx,my;

    //get point at mid of curve
    midparam=(startparam+endparam)/2.0;
    evalfunc(midparam,&mx,&my,evaldata);

    //is it within tolerance?
    if(distlinepoint(x1,y1,x2,y2,mx,my)<=tol)
        return;

    //midpoint of this subcurve is to far from vectorized version, recurse...
    docurve_recursive(x1,y1,mx,my,startparam,midparam,tol,evaldata,evalfunc,callbackdata,callback);
    callback(mx,my,callbackdata);
    docurve_recursive(mx,my,x1,y1,midparam,endparam,tol,evaldata,evalfunc,callbackdata,callback);
}



static void docurve(double x1,double y1,double x2,double y2,double startparam,double endparam,double tol,void* evaldata,void(*evalfunc)(double param,double* x,double* y,void* data),void* callbackdata,void(*callback)(double x,double y,void* data)) {
    callback(x1,y1,callbackdata);
    docurve_recursive(x1,y1,x2,y2,startparam,endparam,tol,evaldata,evalfunc,callbackdata,callback);
    callback(x2,y2,callbackdata);
}



static void doarc(double xc,double yc,double w,double h,double ang1,double ang2,void* data,void(*callback)(double x,double y,void* data)) {

    double rx=w/2.0;
    double ry=h/2.0;
    double evaldata[]={xc,yc,rx,ry};
    double x1,y1,x2,y2;

    while(ang2<ang1)
        ang2+=M_PI*2.0;

    evalellipse(ang1,&x1,&y1,evaldata);
    evalellipse(ang2,&x2,&y2,evaldata);
    docurve(x1,y1,x2,y2,ang1,ang2,CURVETOL,evaldata,evalellipse,data,callback);
}

static void vertexcallback(double x,double y,void* data) {
    glVertex2d(x,y);
}

static void calcendcap(cdCtxCanvas* ctxcanvas,double cx1,double cy1,double cx2,double cy2) {
    double ang,dx,dy,siz,cs,si;

    switch(ctxcanvas->canvas->line_cap) {

        case CD_CAPROUND:
            dx=cx2-cx1;
            dy=cy2-cy1;
            ang=atan2(dy,dx)+M_PI;
            siz=hypot(dx,dy);
            doarc((cx1+cx2)/2.0,(cy1+cy2)/2.0,siz,siz,ang,ang+M_PI,NULL,vertexcallback);
            break;
        case CD_CAPSQUARE:
            dx=cx2-cx1;
            dy=cy2-cy1;
            ang=atan2(dy,dx)-M_PI_2;
            siz=hypot(dx,dy)/2.0;
            cs=cos(ang)*siz;
            si=sin(ang)*siz;
            cx1+=cs;
            cx2+=cs;
            cy1+=si;
            cy2+=si;
            glVertex2d(cx1,cy1);
            glVertex2d(cx2,cy2);
            break;
        default: //ie. CD_CAPFLAT or something invalid
            glVertex2d(cx1,cy1);
            glVertex2d(cx2,cy2);
            break;
    }
}


static void thicksolidline(cdCtxCanvas* ctxcanvas,int x1,int y1,int x2,int y2) {
    //assumes line width>1
    double xa,ya;

    if(!calclinecorners(ctxcanvas,x1,y1,x2,y2,&xa,&ya))
        return;

    begindraw(ctxcanvas,GL_POLYGON);

    calcendcap(ctxcanvas,x1-xa,y1+ya,x1+xa,y1-ya);
    calcendcap(ctxcanvas,x2+xa,y2-ya,x2-xa,y2+ya);

    enddraw(ctxcanvas);

}

static void cdline(cdCtxCanvas* ctxcanvas, int x1, int y1, int x2, int y2) {
    if(ctxcanvas->canvas->line_width<=1) {
        if(ctxcanvas->canvas->line_style!=CD_CUSTOM) {
            begindraw(ctxcanvas,GL_LINES);
            glVertex2i(x1,y1);
            glVertex2i(x2,y2);
        }
        else {
            //todo: draw custom dashes
        }
    }
    else {
        if(ctxcanvas->canvas->line_style==CD_CONTINUOUS) {
            thicksolidline(ctxcanvas,x1,y1,x2,y2);
        }
        else {
            //draw dashed line

        }
    }
}

static void cdpoly(cdCtxCanvas* ctxcanvas, int mode, cdPoint* points, int n) {

}

static void cdrect(cdCtxCanvas* ctxcanvas, int xmin, int xmax, int ymin, int ymax) {
    begindraw(ctxcanvas,GL_LINE_LOOP);
    glVertex2i(xmin,ymin);
    glVertex2i(xmax,ymin);
    glVertex2i(xmax,ymax);
    glVertex2i(xmin,ymax);

}

static void cdbox(cdCtxCanvas* ctxcanvas, int xmin, int xmax, int ymin, int ymax) {
    begindraw(ctxcanvas,GL_QUADS);
    glVertex2i(xmin,ymin);
    glVertex2i(xmax,ymin);
    glVertex2i(xmax,ymax);
    glVertex2i(xmin,ymax);
}







static void cdarc(cdCtxCanvas* ctxcanvas, int xc, int yc, int w, int h, double angle1, double angle2) {
    begindraw(ctxcanvas,GL_LINE_STRIP);
    doarc(xc,yc,w,h,angle1*CD_DEG2RAD,angle2*CD_DEG2RAD,NULL,vertexcallback);
    enddraw(ctxcanvas);
}

static void cdsector(cdCtxCanvas* ctxcanvas, int xc, int yc, int w, int h, double angle1, double angle2) {
    begindraw(ctxcanvas,GL_TRIANGLE_FAN);
    glVertex2i(xc,yc);
    doarc(xc,yc,w,h,angle1*CD_DEG2RAD,angle2*CD_DEG2RAD,NULL,vertexcallback);
    enddraw(ctxcanvas);
}

static void cdchord(cdCtxCanvas* ctxcanvas, int xc, int yc, int w, int h, double angle1, double angle2) {
    begindraw(ctxcanvas,GL_POLYGON);
    doarc(xc,yc,w,h,angle1*CD_DEG2RAD,angle2*CD_DEG2RAD,NULL,vertexcallback);
    enddraw(ctxcanvas);
}

static void cdtext(cdCtxCanvas* ctxcanvas, int x, int y, const char *s, int len) {

}



static int cdfont(cdCtxCanvas* ctxcanvas, const char *type_face, int style, int size) {
    return CD_OK;
}

static void cdputimagerectmap(cdCtxCanvas* ctxcanvas, int iw, int ih, const unsigned char *index, const long *colors, int x, int y, int w, int h, int xmin, int xmax, int ymin, int ymax) {

}

static void cdputimagerectrgb(cdCtxCanvas* ctxcanvas, int iw, int ih, const unsigned char *r, const unsigned char *g, const unsigned char *b, int x, int y, int w, int h, int xmin, int xmax, int ymin, int ymax) {

}



static void cdclear(cdCtxCanvas* ctxcanvas) {
    glClear(GL_COLOR_BUFFER_BIT);
}

static long cdforeground(cdCtxCanvas* ctxcanvas,long color) {
    //debug("foreground");
    glColor3ub((color>>16)&0xff,(color>>8)&0xff,color&0xff);
    return color;
}

static long cdbackground(cdCtxCanvas* ctxcanvas,long color) {
   // debug("background");
    unsigned char r,g,b;
    cdDecodeColor(color,&r,&g,&b);
    glClearColor((double)r/255.0,(double)g/255.0,(double)b/255.0,0.0);
    return color;
}


static int cdlinestyle(cdCtxCanvas* ctxcanvas,int style) {

    //setup gl dashes for single pixel wide lines
   switch(style) {
    case CD_DASHED:
        glLineStipple(3,0x3f3f);
        glEnable(GL_LINE_STIPPLE);
        break;
    case CD_DOTTED:
        glLineStipple(3,0x5555);
        glEnable(GL_LINE_STIPPLE);
        break;
    case CD_DASH_DOT_DOT:
        glLineStipple(3,0x5757);
        glEnable(GL_LINE_STIPPLE);
        break;
    case CD_DASH_DOT:
        glLineStipple(3,0x2727);
        glEnable(GL_LINE_STIPPLE);
        break;
    default: //ie. continuous or custom
        glDisable(GL_LINE_STIPPLE);
   }


    return style;

   /*
   enum {
 CD_CONTINUOUS,
 CD_DASHED,
 CD_DOTTED,
 CD_DASH_DOT,
 CD_DASH_DOT_DOT,
 CD_CUSTOM
};
*/
}


static void cdinittable(cdCanvas* canvas)
{
    /*
     void   (*cxPixel)(cdCtxCanvas* ctxcanvas, int x, int y, long color);
  void   (*cxLine)(cdCtxCanvas* ctxcanvas, int x1, int y1, int x2, int y2);
  void   (*cxPoly)(cdCtxCanvas* ctxcanvas, int mode, cdPoint* points, int n);
  void   (*cxRect)(cdCtxCanvas* ctxcanvas, int xmin, int xmax, int ymin, int ymax);
  void   (*cxBox)(cdCtxCanvas* ctxcanvas, int xmin, int xmax, int ymin, int ymax);
  void   (*cxArc)(cdCtxCanvas* ctxcanvas, int xc, int yc, int w, int h, double angle1, double angle2);
  void   (*cxSector)(cdCtxCanvas* ctxcanvas, int xc, int yc, int w, int h, double angle1, double angle2);
  void   (*cxChord)(cdCtxCanvas* ctxcanvas, int xc, int yc, int w, int h, double angle1, double angle2);
  void   (*cxText)(cdCtxCanvas* ctxcanvas, int x, int y, const char *s, int len);
  void   (*cxKillCanvas)(cdCtxCanvas* ctxcanvas);
  int    (*cxFont)(cdCtxCanvas* ctxcanvas, const char *type_face, int style, int size);
  void   (*cxPutImageRectMap)(cdCtxCanvas* ctxcanvas, int iw, int ih, const unsigned char *index, const long *colors, int x, int y, int w, int h, int xmin, int xmax, int ymin, int ymax);
  void   (*cxPutImageRectRGB)(cdCtxCanvas* ctxcanvas, int iw, int ih, const unsigned char *r, const unsigned char *g, const unsigned char *b, int x, int y, int w, int h, int xmin, int xmax, int ymin, int ymax);


    // all the following function pointers can be NULL

  void   (*cxFlush)(cdCtxCanvas* ctxcanvas);
  void   (*cxClear)(cdCtxCanvas* ctxcanvas);
  long   (*cxBackground)(cdCtxCanvas* ctxcanvas, long color);
  long   (*cxForeground)(cdCtxCanvas* ctxcanvas, long color);

*/

    canvas->cxPixel = cdpixel;
    canvas->cxLine = cdline;
    canvas->cxPoly = cdpoly;
    canvas->cxRect = cdrect;
    canvas->cxBox = cdbox;
    canvas->cxArc = cdarc;
    canvas->cxSector = cdsector;
    canvas->cxChord = cdchord;
    canvas->cxText = cdtext;
    canvas->cxKillCanvas=cdkillcanvas;
    canvas->cxFont = cdfont;
    canvas->cxPutImageRectMap = cdputimagerectmap;
    canvas->cxPutImageRectRGB = cdputimagerectrgb;



    canvas->cxFlush = cdflush;
    canvas->cxClear = cdclear;
    canvas->cxActivate = cdactivate;
    canvas->cxForeground = cdforeground;
    canvas->cxBackground = cdbackground;


    canvas->cxLineStyle = cdlinestyle;

/*
    canvas->cxPutImageRectRGB = cdputimagerectrgb;
    canvas->cxPutImageRectRGBA = cdputimagerectrgba;
    canvas->cxScrollArea = cdscrollarea;
    canvas->cxFLine = cdfline;
    canvas->cxFPoly = cdfpoly;
    canvas->cxFRect = cdfrect;
    canvas->cxFBox = cdfbox;
    canvas->cxFArc = cdfarc;
    canvas->cxFSector = cdfsector;
    canvas->cxFChord = cdfchord;
    canvas->cxFText = cdftext;
    canvas->cxClip = cdclip;
    canvas->cxClipArea = cdcliparea;
    canvas->cxBackOpacity = cdbackopacity;
    canvas->cxWriteMode = cdwritemode;
    canvas->cxLineStyle = cdlinestyle;
    canvas->cxLineWidth = cdlinewidth;
    canvas->cxLineCap = cdlinecap;
    canvas->cxLineJoin = cdlinejoin;
    canvas->cxInteriorStyle = cdinteriorstyle;
    canvas->cxHatch = cdhatch;
    canvas->cxStipple = cdstipple;
    canvas->cxPattern = cdpattern;
    canvas->cxFont = cdfont;
    canvas->cxNativeFont = cdnativefont;
    canvas->cxTextAlignment = cdtextalignment;
    canvas->cxTextOrientation = cdtextorientation;
    canvas->cxPalette = cdpalette;
    canvas->cxBackground = cdbackground;
    canvas->cxForeground = cdforeground;
    canvas->cxFClipArea = cdfcliparea;
    canvas->cxTransform = cdtransform;
    canvas->cxKillCanvas = cdkillcanvas;
    canvas->cxGetImageRGB = cdgetimagergb;
    canvas->cxScrollArea = cdscrollarea;
    canvas->cxCreateImage = cdcreateimage;
    canvas->cxKillImage = cdkillimage;
    canvas->cxGetImage = cdgetimage;
    canvas->cxPutImageRect = cdputimagerect;
    canvas->cxNewRegion = cdnewregion;
    canvas->cxIsPointInRegion = cdispointinregion;
    canvas->cxOffsetRegion = cdoffsetregion;
    canvas->cxGetRegionBox = cdgetregionbox;
    canvas->cxActivate = cdactivate;
    canvas->cxDeactivate = cddeactivate;
    canvas->cxGetFontDim = cdgetfontdim;
    canvas->cxGetTextSize = cdgettextsize;*/
}



static cdContext cdGLContext =
{
  CD_CAP_ALL & ~(CD_CAP_GETIMAGERGB|CD_CAP_IMAGESRV|CD_CAP_REGION|CD_CAP_FONTDIM|CD_CAP_TEXTSIZE),
  0,
  cdcreatecanvas,
  cdinittable,
  NULL,
  NULL,
};

cdContext* cdContextGL(void) {
    return &cdGLContext;
}

